package ldh.maker.code;

import javafx.scene.control.TreeItem;
import ldh.common.Pageable;
import ldh.database.Column;
import ldh.database.Table;
import ldh.maker.database.TableInfo;
import ldh.maker.freemaker.*;
import ldh.maker.util.*;
import ldh.maker.vo.DBConnectionData;
import ldh.maker.vo.SettingData;
import ldh.maker.vo.SettingJson;
import ldh.maker.vo.TreeNode;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;

/**
 * Created by ldh on 2017/4/6.
 */
public class CreateCode {

    protected SettingData data;
    protected TreeItem<TreeNode> treeItem;
    protected Table table;
    protected String dbName;

    protected PojoMaker pojoMaker;
    protected PojoWhereMaker pojoWhereMaker;
    protected DaoMaker daoMaker;
    protected MapperMaker mapperMaker;
    protected ServiceInterfaceMaker serviceInterfaceMaker;
    protected ServiceImplMaker serviceImplMaker;
    protected ServiceMaker serviceMaker;
    protected ControllerMaker controllerMaker;

    protected String root;
    protected Class<?> key = null;
    protected Map<String, String[]> resourceMap = new HashMap<>();

    public static volatile boolean isCreate = true;

    public CreateCode() {}

    public CreateCode(SettingData data, TreeItem<TreeNode> treeItem, String dbName, Table table) {
        this.data = data;
        this.treeItem = treeItem;
        this.table = table;
        this.dbName = dbName;
        root = treeItem.getParent().getParent().getParent().getValue().getText();
    }

    public void create() {
        if (data == null) return;
        String pojoPath = createPath(data.getPojoPackageProperty());
        if (!table.isCreate()) return;

        Class<?> key = null;
        if (table.getPrimaryKey() != null && !table.getPrimaryKey().isComposite()) {
            Column c = table.getPrimaryKey().getColumns().iterator().next();
            key = c.getPropertyClass();
        }

        KeyMaker keyMaker = null;
        if (table.getPrimaryKey() == null || table.getPrimaryKey().isComposite()) {
//            KeyMaker keyM = new KeyMaker()
//                    .pack(data.getPojoPackageProperty())
////                    .extend(extendsClass, extendsName)
//                    .primaryKey(table.getPrimaryKey())
//                    .outPath(pojoPath)
//                    .make();
//            keyMaker = keyM;
//
//            table.setCreate(false);
            System.out.println("WARN!!!! " + table.getName() + " 是复合主键，软件暂时不支持复合主键");
            return;
        }

        if (isCreate) {
            createOnce();
            isCreate = false;
        }

        pojoMaker = buildPojoMaker(pojoPath, key, keyMaker);
        for (Column column : table.getColumnList()) {
            String keyId = treeItem.getParent().getValue().getId() + "_" + dbName + "_" + table.getName() + "_" + column.getName();
            EnumStatusMaker enumStatusMaker = EnumFactory.getInstance().get(keyId);
            if (enumStatusMaker != null) {
                String p = createPath(enumStatusMaker.getPack());
                enumStatusMaker.author(data.getAuthorProperty());
                enumStatusMaker.description(table.getComment());
                enumStatusMaker.outPath(p).make();
                pojoMaker.imports(enumStatusMaker);
                column.setJavaType(enumStatusMaker.getSimpleName());
            }

        }
        pojoMaker.make();

        buildEnumController();

        if (!table.isMiddle()) {
            pojoWhereMaker = buildPojoWhereMaker(pojoMaker, key, keyMaker).make();
        }

        daoMaker = buildDaoMaker(pojoMaker, pojoWhereMaker, key, keyMaker);

        mapperMaker = buildMapperMaker(daoMaker, key, keyMaker).make();

        if (keyMaker != null) return;
        daoMaker.make();

        if (!table.isMiddle()) {
            if (data.getServiceInterface()) {
                serviceInterfaceMaker = buildServiceInterfaceMaker(pojoMaker, pojoWhereMaker, key, keyMaker).make();
                serviceImplMaker = buildServiceImplMaker(pojoMaker, pojoWhereMaker, daoMaker, serviceInterfaceMaker, key, keyMaker).make();
//                controllerMaker = buildControllerMaker(key, keyMaker, data, pojoMaker, pojoWhereMaker, serviceInterfaceMaker).make();
            } else {
                serviceMaker = buildServiceMaker(pojoMaker, pojoWhereMaker, daoMaker, key, keyMaker).make();
//                controllerMaker = buildControllerMaker(key, keyMaker, data, pojoMaker, pojoWhereMaker, serviceMaker).make();
            }
        }

        try {
            createOther();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    protected void createOnce() {
        String pojoPackage = data.getPojoPackageProperty();
        buildMybatisConfigMaker();  // create mybatis-config.xml
        buildApplicationBootMaker(); // create ApplicationBoot.java
//        buildApplicationPropertiesMaker(); // create application.properties
        buildApplicationYmlMaker(); // create application.properties
        buildPomXmlMaker(pojoPackage);  // create pom.xml
        buildMybatisConfigurationMaker();
//        buildLog4jPropertiesMaker(pojoPackage);
        buildLogbackMaker();
        buildSwaggerMaker();
        buildCrossDomainMaker();

//        buildTest();
    }

    public String getProjectName() {
        return data.getProjectNameProperty();
    }

    protected void buildCrossDomainMaker() {
        if (!data.getSettingJson().isCrossDomain()) return;
        String projectRootPackage = getProjectRootPackage(data.getPojoPackageProperty());
        new SimpleJavaMaker()
                .projectRootPackage(projectRootPackage)
                .auhtor(data.getAuthorProperty())
                .ftl("CorsConfig.ftl")
                .fileName("CorsConfig.java")
                .outPath(createPath(projectRootPackage + ".configuration"))
                .make();
    }

    protected void buildTest() {
        String projectRootPackage = getProjectRootPackage(data.getServicePackageProperty());
        new SimpleJavaMaker()
                .projectRootPackage(projectRootPackage)
                .auhtor(data.getAuthorProperty())
                .ftl("TestServiceTest.ftl")
                .fileName("TestUiServiceTest.java")
                .outPath(createTestPath(projectRootPackage))
                .make();
    }

    private ControllerMaker buildControllerMaker(Class<?> key, KeyMaker keyMaker, SettingData data, PojoMaker pojoMaker, PojoWhereMaker pojoWhereMaker, ServiceInterfaceMaker serviceInterfaceMaker) {
        String controllerPath = createPath(data.getControllerPackageProperty());
        String beanName = FreeMakerUtil.firstUpper(table.getJavaName());
        return new ControllerMaker()
              .pack(data.getControllerPackageProperty())
              .author(data.getAuthorProperty())
              .description(table.getComment())
              .outPath(controllerPath)
              .key(key, keyMaker)
              .dbName(dbName)
              .treeId(treeItem.getParent().getValue().getId())
              .className(beanName + "Controller")
              .beanMaker(pojoMaker)
              .beanWhereMaker(pojoWhereMaker)
              .service(serviceInterfaceMaker)
              .imports(pojoWhereMaker)
              .table(table);
    }

    private ControllerMaker buildControllerMaker(Class<?> key, KeyMaker keyMaker, SettingData data, PojoMaker pojoMaker, PojoWhereMaker pojoWhereMaker, ServiceMaker serviceMaker) {
        String controllerPath = createPath(data.getControllerPackageProperty());
        String beanName = FreeMakerUtil.firstUpper(table.getJavaName());
        return new ControllerMaker()
                .pack(data.getControllerPackageProperty())
                .author(data.getAuthorProperty())
                .description(table.getComment())
                .outPath(controllerPath)
                .key(key, keyMaker)
                .dbName(dbName)
                .treeId(treeItem.getParent().getValue().getId())
                .className(beanName + "Controller")
                .beanMaker(pojoMaker)
                .beanWhereMaker(pojoWhereMaker)
                .service(serviceMaker)
                .imports(pojoWhereMaker)
                .table(table);
    }

    private ServiceImplMaker buildServiceImplMaker(PojoMaker pojoMaker, PojoWhereMaker pojoWhereMaker,
                DaoMaker daoMaker, ServiceInterfaceMaker serviceInterfaceMaker, Class<?> key, KeyMaker keyMaker) {
        String serviceImplPath = createPath(data.getServicePackageProperty() + ".impl");
        String beanName = FreeMakerUtil.firstUpper(table.getJavaName());
        return new ServiceImplMaker()
                .pack(data.getServicePackageProperty() + ".impl")
                .author(data.getAuthorProperty())
                .description(table.getComment())
                .outPath(serviceImplPath)
                .key(key, keyMaker)
                .dbName(dbName)
                .treeId(treeItem.getParent().getValue().getId())
                .className(beanName + "ServiceImpl")
                .beanWhereMaker(pojoMaker)
                .daoMaker(daoMaker)
                .extend(serviceInterfaceMaker)
                .imports(pojoWhereMaker)
                .table(table);
    }

    private ServiceInterfaceMaker buildServiceInterfaceMaker(PojoMaker pojoMaker, PojoWhereMaker pojoWhereMaker, Class<?> key, KeyMaker keyMaker) {
        String servicePath = createPath(data.getServicePackageProperty());
        String beanName = FreeMakerUtil.firstUpper(table.getJavaName());
        return new ServiceInterfaceMaker()
                .pack(data.getServicePackageProperty())
                .author(data.getAuthorProperty())
                .description(table.getComment())
                .outPath(servicePath)
                .key(key, keyMaker)
                .dbName(dbName)
                .treeId(treeItem.getParent().getValue().getId())
                .className(beanName + "Service")
                .beanWhereMaker(pojoMaker)
                .imports(pojoWhereMaker)
                .table(table);
    }

    private ServiceMaker buildServiceMaker(PojoMaker pojoMaker, PojoWhereMaker pojoWhereMaker, DaoMaker daoMaker, Class<?> key, KeyMaker keyMaker) {
        String servicePath = createPath(data.getServicePackageProperty());
        String beanName = FreeMakerUtil.firstUpper(table.getJavaName());
        return new ServiceMaker()
                .pack(data.getServicePackageProperty())
                .author(data.getAuthorProperty())
                .description(table.getComment())
                .outPath(servicePath)
                .key(key, keyMaker)
                .dbName(dbName)
                .treeId(treeItem.getParent().getValue().getId())
                .className(beanName + "Service")
                .beanWhereMaker(pojoMaker)
                .daoMaker(daoMaker)
                .imports(pojoWhereMaker)
                .table(table);
    }

    private MapperMaker buildMapperMaker(DaoMaker daoMaker, Class<?> key, KeyMaker keyMaker) {
        String xmlPath = createXmlPath(data.getXmlPackageProperty());
        return new MapperMaker()
                .namespace(daoMaker.getName())
                .table(table)
//                .typeHandlers(typeHandlers)
                .outPath(xmlPath)
                .key(key, keyMaker);
    }

    private DaoMaker buildDaoMaker(PojoMaker pojoMaker, PojoWhereMaker pojoWhereMaker, Class<?> key, KeyMaker keyMaker) {
        String daoPath = createPath(data.getDaoPackageProperty());
        String beanName = FreeMakerUtil.firstUpper(table.getJavaName());
        return new DaoMaker()
                .pack(data.getDaoPackageProperty())
                .author(data.getAuthorProperty())
                .description(table.getComment())
                .outPath(daoPath)
                .dbName(dbName)
                .treeId(treeItem.getParent().getValue().getId())
                .key(key, keyMaker)
                .className(beanName + "Dao")
                .beanMaker(pojoMaker)
                .imports(pojoWhereMaker)
                .table(table);
    }

    private PojoWhereMaker buildPojoWhereMaker(PojoMaker pojoMaker, Class<?> key, KeyMaker keyMaker) {
        String pojoWherePath = createPath(data.getPojoPackageProperty()+ ".where");
        String beanName = FreeMakerUtil.firstUpper(table.getJavaName());
        return new PojoWhereMaker()
                .pack(data.getPojoPackageProperty() + ".where")
                .outPath(pojoWherePath)
                .className(beanName + "Where")
                .author(data.getAuthorProperty())
                .extend(pojoMaker)
                .table(table)
                .key(key, keyMaker)
                .implement(Pageable.class)
                .serializable(pojoMaker.isSerializable());
    }

    private PojoMaker buildPojoMaker(String pojoPath, Class<?> key, KeyMaker keyMaker) {
        PojoMaker pojoMaker = new PojoMaker()
                .pack(data.getPojoPackageProperty())
                .author(data.getAuthorProperty())
//                .extend(extendsClass, extendsName)
                .table(table)
                .key(key, keyMaker)
                .outPath(pojoPath);
        SettingJson settingJson = data.getSettingJson();
        if (settingJson != null) {
            pojoMaker.isLombok(settingJson.isLombok());
        }
        return pojoMaker;
    }

    private void buildMybatisConfigMaker() {
        TableInfo tableInfo = DbInfoFactory.getInstance().get(treeItem.getValue().getParent().getId() + "_" + dbName);
        String resourcePath = createResourcePath();
        File file = new File(resourcePath + "/mybatis-config.xml");
//        if (file.exists()) return;
        new MybatisConfigMaker()
                .pojoPackage(data.getPojoPackageProperty())
                .tables(tableInfo.getTables().values())
                .outPath(resourcePath)
                .make();
    }

    protected void buildApplicationBootMaker() {
        String projectRootPackage = data.getBasePackageProperty();
        String resourcePath = createPath(projectRootPackage);
//        if (file.exists()) return;
        new ApplicationBootMaker()
                .projectRootPackage(projectRootPackage)
                .author(data.getAuthorProperty())
                .outPath(resourcePath)
                .make();
    }

    public void add(String ftl, String... outDir) {
        resourceMap.put(ftl, outDir);
    }

    protected void buildApplicationPropertiesMaker() {
        String projectRootPackage = data.getBasePackageProperty();
        String resourcePath = createResourcePath("resources-dev");
        TreeNode treeNode = treeItem.getValue().getParent();
        DBConnectionData data = (DBConnectionData) treeNode.getData();
        new ApplicationPropertiesMaker()
                .projectRootPackage(projectRootPackage)
                .projectName(this.getProjectName())
                .dBConnectionData(data)
                .dbName(dbName)
                .outPath(resourcePath)
                .make();

        resourcePath = createResourcePath("resources-prod");
        new ApplicationPropertiesMaker()
                .projectRootPackage(projectRootPackage)
                .projectName(this.getProjectName())
                .dBConnectionData(data)
                .dbName(dbName)
                .outPath(resourcePath)
                .make();
    }

    protected void buildApplicationYmlMaker() {
        String projectRootPackage = data.getBasePackageProperty();
        String resourcePath = createResourcePath("resources-dev");
        TreeNode treeNode = treeItem.getValue().getParent();
        DBConnectionData data = (DBConnectionData) treeNode.getData();
        new ApplicationPropertiesMaker()
                .projectRootPackage(projectRootPackage)
                .projectName(this.getProjectName())
                .dBConnectionData(data)
                .ftl("application.ftl")
                .fileName("application.yml")
                .dbName(dbName)
                .outPath(resourcePath)
                .make();

        resourcePath = createResourcePath("resources-prod");
        new ApplicationPropertiesMaker()
                .projectRootPackage(projectRootPackage)
                .projectName(this.getProjectName())
                .dBConnectionData(data)
                .ftl("application.ftl")
                .fileName("application.yml")
                .dbName(dbName)
                .outPath(resourcePath)
                .make();

        resourcePath = createResourcePath("resources");
        new ApplicationPropertiesMaker()
                .projectRootPackage(projectRootPackage)
                .projectName(this.getProjectName())
                .dBConnectionData(data)
                .ftl("application.ftl")
                .fileName("application.yml")
                .dbName(dbName)
                .outPath(resourcePath)
                .make();

        resourcePath = createTestResourcePath("resources");
        new ApplicationPropertiesMaker()
                .projectRootPackage(projectRootPackage)
                .projectName(this.getProjectName())
                .dBConnectionData(data)
                .ftl("applicationTest.ftl")
                .fileName("application.yml")
                .dbName(dbName)
                .outPath(resourcePath)
                .make();
    }

    protected void buildPomXmlMaker(String packagePath) {
        String projectRootPackage = getProjectRootPackage(packagePath);
        String resourcePath = createPomPath();
        new PomXmlMaker()
                .projectRootPackage(projectRootPackage)
                .projectName(this.getProjectName())
                .outPath(resourcePath)
                .make();
    }

    private void buildMybatisConfigurationMaker() {
        String projectRootPackage = getProjectRootPackage(data.getPojoPackageProperty());
        String resourcePath = createPath(projectRootPackage + ".configuration");
        String mybatisXmlPackage = data.getXmlPackageProperty().replace(".", "/");
        new MybatisConfigurationMaker()
                .projectRootPackage(projectRootPackage)
                .author(data.getAuthorProperty())
                .mybatisXmlPackage(mybatisXmlPackage)
                .daoPackage(data.getDaoPackageProperty())
                .outPath(resourcePath)
                .make();
    }

    protected void buildLog4jPropertiesMaker(String packagePath) {
        String projectRootPackage = getProjectRootPackage(packagePath);
        String resourcePath = createResourcePath();
        new Log4jPropertiesMaker()
                .projectRootPackage(projectRootPackage)
                .outPath(resourcePath)
                .make();
    }

    private void buildLogbackMaker() {
        String projectRootPackage = getProjectRootPackage(data.getPojoPackageProperty());
        String resourcePath = createResourcePath();
        new LogbackMaker()
                .projectRootPackage(projectRootPackage)
                .outPath(resourcePath)
                .make();

        new LogbackMaker()
                .projectRootPackage(projectRootPackage)
                .outPath(createResourcePath("resources-prod"))
                .make();
    }

    protected void buildSwaggerMaker() {
        String projectRootPackage = getProjectRootPackage(data.getPojoPackageProperty());
        String resourcePath = createPath(projectRootPackage + ".configuration");
//        new SimpleJavaMaker()
//                .projectRootPackage(projectRootPackage)
//                .auhtor(data.getAuthorProperty())
//                .outPath(resourcePath)
//                .data("controllerPackage", data.getControllerPackageProperty())
//                .ftl("StaticResourceConfiguration.ftl")
//                .fileName("StaticResourceConfiguration.java")
//                .make();
        new SimpleJavaMaker()
                .projectRootPackage(projectRootPackage)
                .auhtor(data.getAuthorProperty())
                .outPath(resourcePath)
                .data("controllerPackage", data.getControllerPackageProperty())
                .ftl("SwaggerConfig.ftl")
                .fileName("SwaggerConfig.java")
                .make();
    }

    protected void createOther() {
        if (!table.isMiddle()) {
            if (data.getServiceInterface()) {
                controllerMaker = buildControllerMaker(key, null, data, pojoMaker, pojoWhereMaker, serviceInterfaceMaker).make();
            } else {
                controllerMaker = buildControllerMaker(key, null, data, pojoMaker, pojoWhereMaker, serviceMaker).make();
            }
        }
    }

    protected String createPath(String pk) {
        String path = FileUtil.getSourceRoot();
        String[] pks = pk.split("\\.");
        List<String> dirs = new ArrayList<>(Arrays.asList("code", root, getProjectName(), "src", "main", "java"));
        for (String p : pks) {
            dirs.add(p);
        }
        path = makePath(path, dirs);
        return path;
    }

    protected String createTestPath(String pk) {
        String path = FileUtil.getSourceRoot();
        String[] pks = pk.split("\\.");
        List<String> dirs = new ArrayList<>(Arrays.asList("code", root, getProjectName(), "src", "test", "java"));
        for (String p : pks) {
            dirs.add(p);
        }
        path = makePath(path, dirs);
        return path;
    }

    protected String makePath(String path, List<String> dirs) {
        return CopyDirUtil.makePath(path, dirs);
    }

    private String createXmlPath(String xmlPackageProperty) {
        String path = FileUtil.getSourceRoot();
        String[] pks = xmlPackageProperty.split("\\.");
        List<String> dirs = new ArrayList<>(Arrays.asList("code", root, getProjectName(), "src", "main", "resources"));
        for (String p : pks) {
            dirs.add(p);
        }
        path = makePath(path, dirs);
        return path;
    }

    protected String createResourcePath() {
        String path = FileUtil.getSourceRoot();
        List<String> dirs = new ArrayList<>(Arrays.asList("code", root, getProjectName(), "src", "main", "resources"));
        path = makePath(path, dirs);
        return path;
    }

    protected String createResourcePath(String... resourceNames) {
        String path = FileUtil.getSourceRoot();
        List<String> dirs = new ArrayList<>(Arrays.asList("code", root, getProjectName(), "src", "main"));
        for (String r : resourceNames) {
            dirs.add(r);
        }
        path = makePath(path, dirs);
        return path;
    }

    protected String createTestResourcePath(String... resourceNames) {
        String path = FileUtil.getSourceRoot();
        List<String> dirs = new ArrayList<>(Arrays.asList("code", root, getProjectName(), "src", "test"));
        for (String r : resourceNames) {
            dirs.add(r);
        }
        path = makePath(path, dirs);
        return path;
    }

    protected String createPomPath() {
        String path = FileUtil.getSourceRoot();
        List<String> dirs = new ArrayList<>(Arrays.asList("code", root, getProjectName()));
        path = makePath(path, dirs);
        return path;
    }

    protected String getProjectRootPackage(String pojoPackage) {
        int t = pojoPackage.lastIndexOf(".");
        return pojoPackage.substring(0, t);
    }

    protected String createJspPath() {
        String path = FileUtil.getSourceRoot();
        List<String> dirs = new ArrayList<>(Arrays.asList("code", root, getProjectName(), "src", "main", "webapp", "WEB-INF", "jsp"));
        path = makePath(path, dirs);
        return path;
    }

    protected String createFtlPath(String dir) {
        String path = FileUtil.getSourceRoot();
        List<String> dirs = new ArrayList<>(Arrays.asList("code", root, getProjectName(), "src", "main", "resources", "ftl", dir));
        path = makePath(path, dirs);
        return path;
    }

    protected String createJspPath(String... dirs) {
        String path = FileUtil.getSourceRoot();
        List<String> dirst = new ArrayList<>(Arrays.asList("code", root, getProjectName(), "src", "main", "webapp", "WEB-INF"));
        for (String d : dirs) {
            dirst.add(d.toString());
        }
        path = makePath(path, dirst);
        return path;
    }

    protected String createJsPath() {
        String path = FileUtil.getSourceRoot();
        List<String> dirs = new ArrayList<>(Arrays.asList("code", root, getProjectName(), "src", "main", "webapp", "resource", "js"));
        path = makePath(path, dirs);
        return path;
    }

    protected String createPath(String...paths) {
        String path = FileUtil.getSourceRoot();
        List<String> dirs = new ArrayList<>(Arrays.asList("code", root, getProjectName()));
        for (String d : paths) {
            dirs.add(d.toString());
        }
        path = makePath(path, dirs);
        return path;
    }

    protected void copyResources(String srcDir, List<String> dirs, String... targetDirs) throws IOException {
        String root = FileUtil.getSourceRoot();
        Enumeration<URL> urls = this.getClass().getClassLoader().getResources(srcDir);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            String srcFile = url.getPath();
            CopyDirUtil.copyResourceDir(srcFile, root, copyListAndAdd(dirs, targetDirs));
        }
    }

    private List<String> copyListAndAdd(List<String> dirs, String...strings) {
        List<String> result = new ArrayList<>();
        for (String dir : dirs) {
            result.add(dir);
        }
        for (String s : strings) {
            result.add(s);
        }
        return result;
    }

    protected void copyFile(String file, List<String> dirs) throws IOException {
        String root = FileUtil.getSourceRoot();
        URL url = Thread.currentThread().getContextClassLoader().getResource(file);
        String srcFile = url.getFile();
        CopyDirUtil.copyFile(srcFile, root, dirs);
    }

    protected void buildEnumController() {
        String controllerPath = createPath(data.getControllerPackageProperty());
        EnumControllerMaker enumControllerMaker = new EnumControllerMaker();
        enumControllerMaker.pack(data.getControllerPackageProperty()).outPath(controllerPath);
        for (Map.Entry<String, EnumStatusMaker> entry : EnumFactory.getInstance().getEnumStatusMakerMap().entrySet()){
            enumControllerMaker.add(entry.getValue());
        }
        enumControllerMaker.author(data.getAuthorProperty());
        enumControllerMaker.description(table.getComment());
        enumControllerMaker.make();
    }
}
